using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Diagnostics;
using System.Xml.Serialization;
using System.IO;

namespace Kopain
{

    /// <summary>This class represents a Savefile - it's a file to Open and Save.
    /// Yeah - you know - like an ordinary... file.
    /// </summary>
    class Savefile
    {

#region Private properties and attributes.

        /// <summary>This contains the root where all data about the Savefile is stored.
        /// </summary>
        private NoteRoot _doc;

        /// <summary>This stores the physical name of the Savefile together with path.
        /// </summary>
        /// <example>C:\Data\Myfile.xml</example>
        private string _filenamewpath;

#endregion  //  Private properties and attributes.

#region Constructor.

        /// <summary>This constructor opens the file and stores it's data in an xmldocument.
        /// </summary>
        /// <param name="filename"></param>
        internal Savefile( string filename )
        {
            //  We start by checking the pathes because the stupid System.IO.DirectoryNotFoundException does not have a property for the directory.  Hence we start with checking the pathes before continuing - directory by directory.
            if (!Directory.Exists(Path.GetDirectoryName(filename)))
            {
                throw new Exception.DirectoryWasNotFoundException(Path.GetDirectoryName(filename), null);
            }
            if (!Directory.Exists(Path.GetDirectoryName(Constant.NoteXSDFileNameWPath)))
            {
                throw new Exception.DirectoryWasNotFoundException(Path.GetDirectoryName(Constant.NoteXSDFileNameWPath), null);
            }

            _filenamewpath = filename;

            //  Load and validate document.  This is only for validation.
            //  It would be nice to update so the File isn't read two times.
            //      Look at Setting.cs how it is done there.
            //
            XmlDocument docTemp = new XmlDocument();
            try
            {
                docTemp.Load(filename);

                docTemp.Schemas.Add(null, Constant.NoteXSDFileNameWPath);
                Validate(docTemp);
                //docTemp.Validate(null);

                docTemp = null;

                //  Load and deserialize File for use.
                //
                XmlSerializer serz = new XmlSerializer(typeof(NoteRoot));
                using (FileStream filestream = new FileStream(filename, FileMode.Open))
                {
                    _doc = (NoteRoot)serz.Deserialize(filestream);
                    filestream.Close();
                }
            }
            catch (XmlException exc)
            {
                throw new Exception.CorruptSaveFile(exc);
            }
            catch (System.Xml.Schema.XmlSchemaValidationException exc)
            {
                Debug.Fail("XmlSchemaValidationException.  Maybe it is the wrong version of the Savefile.  Check Validate method.");

                throw; //  Rethrow.
            }  
            catch (FileNotFoundException exc)
            {
                throw new Exception.FileWasNotFoundException(exc.FileName, exc);
            }

            //  Update all Parent attributes.
            SetParent( null, _doc.Note );

        }

#endregion  //Constructor.

#region Public methods.

        /// <summary>This method returns the full filename with path of the Savefile
        /// </summary>
        internal string FilenameWithPath
        {
            get { return _filenamewpath; }
        }


        /// <summary>This retreives the root of the notetree.
        /// </summary>
        internal NoteRoot NoteRoot
        {
            get
            {
                return _doc;
            }
        }

        /// <summary>This method saves the savefile.
        /// </summary>
        internal void Save()
        {

            XmlSerializer serz = new XmlSerializer(typeof(NoteRoot));
            using (StreamWriter writer = new StreamWriter(FilenameWithPath))
            {
                serz.Serialize(writer, NoteRoot);
                writer.Close();
            }
        
        }

        private void Save(XmlDocument doc)
        {

            XmlSerializer serz = new XmlSerializer(typeof(XmlDocument));
            using (StreamWriter writer = new StreamWriter(FilenameWithPath))
            {
                serz.Serialize(writer, doc);  // serz.Serialize(writer, NoteRoot);
                writer.Close();
            }
        
        }

#endregion  //Public methods.

#region Private methods.

        /// <summary>This method is validating and updating in one.
        /// </summary>
        /// <param name="doc"></param>
        private void Validate(XmlDocument doc)
        {
            // Get the version number.  If there is no version we say it is version zero.
            int version = NoteRoot.HasVersion(doc) ? NoteRoot.GetVersion(doc) : 0;

            // Go trhough this kratta, version by version.  Save for each update and then call recursively until we run out of version numbers, which is the final (present) version.
            switch (version)
            {
                case 0:
                    // Set version number.  That is all that is done to upgrade to version 1.
                    //
                    NoteRoot.SetVersion(doc, 1);
                    Save(doc);
                    Validate(doc);
                    break;
                case 1:
                    // Set cursorPosition on every Note.  Set to default 0.
                    //
                    NoteRoot.SetVersion(doc, 2);
                    foreach (XmlElement note in NoteRoot.GetNotes(doc))
                    {
                        NoteType.CreateUpdateCursorPositionAttribute(note, 0);
                    }
                    Save(doc);
                    Validate(doc);
                    break;
                case 2:
                    // Set OpenNoteId on the Root node.  Default "" means no default Note to show.
                    //
                    NoteRoot.SetVersion(doc, 3);
                    NoteRoot.CreateUpdateOpenNoteIdAttribute(doc, string.Empty);
                    Save(doc);
                    Validate(doc);
                    break;
                    // Template:
                    //case n:
                    //NoteRoot.SetVersion(doc, n);
                    //Do - the - update;
                    //Save(doc);
                    //Validate(doc);
                    //break;
                default:
                    // If it comes here we should be finished.  No more vesion updates.  If it was an updated savefile we come here directly.
                    // Do a validation which throws an exception if something is wrong.
                    doc.Validate(null);
                    break;
            }
        }

        /// <summary>This method sets the parent to of an array of Notes.
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="notes"></param>
        private void SetParent( NoteType parent, NoteType[] notes )
        {
            if( null != notes )
            {
                foreach( NoteType note in notes )
                {
                    note.Root = _doc;
                    note.Parent = parent;
                    SetParent( note, note.Note );
                }
            }
        }

#endregion  //Private methods.

    }
}
